ftrace使用实战 您所在的位置:网站首页 linux 性能优化实战 ftrace使用实战

ftrace使用实战

2023-04-02 16:33| 来源: 网络整理| 查看: 265

诉求:遇到一个问题 echo blocked > /sys/class/block/sdb/device/state 报非法参数,想要知道根因,但是对这块内核代码不熟悉,不知道从哪里下手,那就先用ftrace看看内核调用栈,如下所示。

root@rlk:/home/rlk/rlk# echo blocked > /sys/class/block/sdb/device/state bash: echo: write error: Invalid argument

cd /sys/kernel/debug/tracing

设定跟踪的进程pid set_ftrace_pid

查看可以设定的tracer

root@rlk:/home/rlk/rlk# cat /sys/kernel/debug/tracing/available_tracers hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop 设定tracer类型 root@rlk:/sys/kernel/debug/tracing# echo function_graph > current_tracer 开启tracer root@rlk:/sys/kernel/debug/tracing# echo 1 > tracing_on

写文件/sys/class/block/sdb/device/state是一个很快的过程,没有办法精准的知道什么时候把trace打开,所以只能采集相对来说比较泛的数据,为了减少一些无用的数据,可以通过写两个脚本来实现,先抓取echo running > /sys/class/block/sdb/device/state的调用栈。

write.sh

#!/bin/bash sleep 0.3 echo running > /sys/class/block/sdb/device/state

查找进程pid命令花费的时间为0.109s

root@rlk:/home/rlk/rlk# time ps aux | grep write.sh | grep -v grep | awk '{print $2}' 5442 real 0m0.109s user 0m0.019s sys 0m0.121s #!/bin/bash ./write.sh & pid=`ps aux | grep write.sh | grep -v grep | awk '{print $2}'` echo $pid echo $pid > /sys/kernel/debug/tracing/set_ftrace_pid echo 1 > /sys/kernel/debug/tracing/tracing_on

echo 其实就是一个打开文件,然后写文件的过程,用户态写文件会调用vfs_write,在trace日志中抓出如下调用信息。

1) | do_syscall_64() { 1) | __x64_sys_write() { 1) | ksys_write() { 1) | __fdget_pos() { 1) 0.100 us | __fget_light(); 1) 0.291 us | } 1) | vfs_write() { 1) | rw_verify_area() { 1) | security_file_permission() { 1) | apparmor_file_permission() { 1) | common_file_perm() { 1) 0.100 us | aa_file_perm(); 1) 0.290 us | } 1) 0.481 us | } 1) 0.682 us | } 1) 0.872 us | } 1) | __sb_start_write() { 1) | _cond_resched() { 1) 0.090 us | rcu_all_qs(); 1) 0.271 us | } 1) 0.461 us | } 1) | __vfs_write() { 1) | kernfs_fop_write() { 1) | __kmalloc() { 1) 0.090 us | kmalloc_slab(); 1) | _cond_resched() { 1) 0.091 us | rcu_all_qs(); 1) 0.291 us | } 1) 0.090 us | should_failslab(); 1) 0.330 us | memcg_kmem_put_cache(); 1) 1.513 us | } 1) | __check_object_size() { 1) 0.100 us | check_stack_object(); 1) 0.090 us | __virt_addr_valid(); 1) 0.491 us | __check_heap_object(); 1) 1.202 us | } 1) | mutex_lock() { 1) | _cond_resched() { 1) 0.090 us | rcu_all_qs(); 1) 0.261 us | } 1) 0.451 us | } 1) 0.090 us | kernfs_get_active(); 1) | sysfs_kf_write() { //调用sfsfs写接口 1) | dev_attr_store() { 1) | store_state_field() { 1) | mutex_lock() { 1) | _cond_resched() { 1) 0.100 us | rcu_all_qs(); 1) 0.271 us | } 1) 0.461 us | } 1) 0.200 us | scsi_device_set_state(); //在这里设置了device的状态 1) | blk_mq_run_hw_queues() { 1) | blk_mq_run_hw_queue() { 1) 0.390 us | dd_has_work(); 1) 2.084 us | } 1) 2.775 us | } 1) 0.090 us | mutex_unlock(); 1) 4.628 us | } 1) 5.210 us | } 1) 6.191 us | } 1) 0.101 us | kernfs_put_active(); 1) 0.090 us | mutex_unlock(); 1) 0.091 us | kfree(); 1) + 10.841 us | } 1) + 11.071 us | } 1) 0.091 us | kfree(); 1) + 10.841 us | } 1) + 11.071 us | } 1) 0.090 us | __fsnotify_parent(); 1) 0.090 us | fsnotify(); 1) 0.080 us | __sb_end_write(); 1) + 13.415 us | } 1) + 14.006 us | } 1) + 14.187 us | } 1) 0.090 us | fpregs_assert_state_consistent(); 1) + 14.627 us | }

drivers/scsi/scsi_sysfs.c定义了 /sys/class/block/sdx/device/state操作函数,所有块设备相关(ll /sys/class/block/sdx/device/)DEVICE_ATTR都可以在这个文件中找到,测试的内核版本为5.4.0-26-generic

// static const struct { enum scsi_host_state value; char *name; } shost_states[] = { { SHOST_CREATED, "created" }, { SHOST_RUNNING, "running" }, { SHOST_CANCEL, "cancel" }, { SHOST_DEL, "deleted" }, { SHOST_RECOVERY, "recovery" }, { SHOST_CANCEL_RECOVERY, "cancel/recovery" }, { SHOST_DEL_RECOVERY, "deleted/recovery", }, }; const char *scsi_host_state_name(enum scsi_host_state state) { int i; char *name = NULL; for (i = 0; i name = shost_states[i].name; break; } } return name; } static ssize_t store_state_field(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int i, ret; struct scsi_device *sdev = to_scsi_device(dev); enum scsi_device_state state = 0; for (i = 0; i state = sdev_states[i].value; break; } } switch (state) { case SDEV_RUNNING: case SDEV_OFFLINE: break; default: return -EINVAL; } mutex_lock(&sdev->state_mutex); ret = scsi_device_set_state(sdev, state); /* * If the device state changes to SDEV_RUNNING, we need to run * the queue to avoid I/O hang. */ if (ret == 0 && state == SDEV_RUNNING) blk_mq_run_hw_queues(sdev->request_queue, true); mutex_unlock(&sdev->state_mutex); return ret == 0 ? count : -EINVAL; } static ssize_t show_state_field(struct device *dev, struct device_attribute *attr, char *buf) { struct scsi_device *sdev = to_scsi_device(dev); const char *name = scsi_device_state_name(sdev->sdev_state); if (!name) return -EINVAL; return snprintf(buf, 20, "%s\n", name); } static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, show_state_field, store_state_field);

以上问题可以在上面的代码找到答案,当前仅支持设置SDEV_RUNNING,SDEV_OFFLINE两种状态。

DEVICE_ATTR 是一个宏定义,用于在 Linux 设备驱动程序中定义设备属性。它定义了一个名为 dev_attr_ 的静态结构体变量,其中 是属性的名称。该结构体包含了属性的名称、读取和写入函数的指针,以及一些其他属性。

使用 DEVICE_ATTR 宏可以方便地定义设备属性,而无需手动编写结构体和函数。例如,以下代码定义了一个名为 my_attribute 的设备属性:

static ssize_t my_attribute_show(struct device *dev, struct device_attribute *attr, char *buf) { // 读取属性值并将其写入缓冲区 return sprintf(buf, "Hello, world!\n"); } static ssize_t my_attribute_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { // 将缓冲区中的值写入属性 return count; } DEVICE_ATTR(my_attribute, 0644, my_attribute_show, my_attribute_store);

在上面的代码中,my_attribute_show 和 my_attribute_store 分别是读取和写入函数的指针。0644 是属性的访问权限,表示该属性可以被所有者读取和写入,其他用户只能读取。最后一行使用 DEVICE_ATTR 宏定义了 my_attribute 属性。

Linux 内核的 IO 调用栈通常包括以下几个层次:

用户空间调用:应用程序通过系统调用(如 read、write、open 等)向内核发起 IO 请求。

VFS 层:VFS(Virtual File System)是 Linux 内核中的一个抽象层,它负责管理文件系统的挂载、卸载、文件名解析等操作。当应用程序发起 IO 请求时,VFS 层会根据文件系统类型和文件描述符等信息,将请求转发给相应的文件系统。

文件系统层:文件系统层负责具体的 IO 操作,包括读写磁盘、缓存管理、文件系统元数据更新等。不同的文件系统有不同的实现方式,但它们都需要遵循 VFS 层的接口规范。

块设备层:块设备层负责将 IO 请求转换为磁盘操作。它通过与硬件驱动程序的交互,将数据从内核缓冲区写入磁盘或从磁盘读取数据到内核缓冲区。

硬件驱动程序:硬件驱动程序负责与硬件设备进行通信,将 IO 请求转换为硬件操作。它通过与设备控制器的交互,将数据从内存写入磁盘或从磁盘读取数据到内存。

总的来说,Linux 内核的 IO 调用栈是一个由多个层次组成的复杂系统,每个层次都有自己的职责和实现方式。在 IO 请求的处理过程中,数据需要在不同的层次之间传递和转换,因此 IO 性能的优化需要考虑整个调用栈的影响。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有